package org.fle4y.app.server.database;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.sql.SQLException;

/**
 * author : xiehui<br>
 * company : northking <br>
 * Create at: 2012-6-27<br>
 * 
 * @version : 1.0<br>
 *          mailto:flexie@foxmail.com<br>
 *          Description :
 */
public class Cache {
	
	private RandomAccessFile rFile;
	private final static int LENGTH = 1 << 14;
	private final static int MAX_CACHE_SIZE = LENGTH * 3 / 4;
	private Row rData[];
	private Row rWriter[];
	private Row rFirst;
	private Row rLastChecked;
	private String sName;
	private final static int MASK = (LENGTH) - 1;
	private int iFreePos;
	private final static int FREE_POS_POS = 16;
	private final static int INITIAL_FREE_POS = 32;
	private final static int MAX_FREE_COUNT = 1024;
	private CacheFree fRoot;
	private int iFreeCount;
	private int iCacheSize;

	public Cache(String name) {
		sName = name;
		rData = new Row[LENGTH];
		rWriter = new Row[LENGTH];
	}

	public void open(boolean readonly) throws SQLException {
		try {
			boolean exists = false;
			File f = new File(sName);
			if (f.exists() && f.length() > FREE_POS_POS) {
				exists = true;
			}
			rFile = new RandomAccessFile(sName, readonly ? "r" : "rw");
			if (exists) {
				rFile.seek(FREE_POS_POS);
				iFreePos = rFile.readInt();
			} else {
				iFreePos = INITIAL_FREE_POS;
			}
		} catch (Exception e) {
			throw new SQLException("打开" + sName + " 失败 !");
		}
	}

	public void flush() throws SQLException {
		try {
			rFile.seek(FREE_POS_POS);
			rFile.writeInt(iFreePos);
			saveAll();
			rFile.close();
		} catch (Exception e) {
			throw new SQLException("关闭 "+ sName+ " 失败！");
		}
	}

	public void shutdown() throws SQLException {
		try {
			rFile.close();
		} catch (Exception e) {
			throw new SQLException("关闭 "+ sName+" 失败 ！");
		}
	}

	public void free(Row r, int pos, int length) throws SQLException {
		iFreeCount++;
		CacheFree n = new CacheFree();
		n.iPos = pos;
		n.iLength = length;
		if (iFreeCount > MAX_FREE_COUNT) {
			iFreeCount = 0;
		} else {
			n.fNext = fRoot;
		}
		fRoot = n;
		remove(r);
	}

	public void add(Row r) throws SQLException {
		int size = r.iSize;
		CacheFree f = fRoot;
		CacheFree last = null;
		int i = iFreePos;
		while (f != null) {
			if (f.iLength >= size) {
				i = f.iPos;
				size = f.iLength - size;
				if (size < 8) {
					if (last == null) {
						fRoot = f.fNext;
					} else {
						last.fNext = f.fNext;
					}
					iFreeCount--;
				} else {
					f.iLength = size;
					f.iPos += r.iSize;
				}
				break;
			}
			last = f;
			f = f.fNext;
		}
		r.iPos = i;
		if (i == iFreePos) {
			iFreePos += size;
		}
		int k = i & MASK;
		Row before = rData[k];
		if (before == null) {
			before = rFirst;
		}
		r.insert(before);
		iCacheSize++;
		rData[k] = r;
		rFirst = r;
	}

	public Row getRow(int pos, Table t) throws SQLException {
		int k = pos & MASK;
		Row r = rData[k];
		Row start = r;
		while (r != null) {
			int p = r.iPos;
			if (p == pos) {
				return r;
			} else if ((p & MASK) != k) {
				break;
			}
			r = r.rNext;
			if (r == start) {
				break;
			}
		}
		Row before = rData[k];
		if (before == null) {
			before = rFirst;
		}
		try {
			rFile.seek(pos);
			int size = rFile.readInt();
			byte buffer[] = new byte[size];
			rFile.read(buffer);
			ByteArrayInputStream bin = new ByteArrayInputStream(buffer);
			DataInputStream in = new DataInputStream(bin);
			r = new Row(t, in, pos, before);
			r.iSize = size;
		} catch (IOException e) {
			e.printStackTrace();
			throw new SQLException("读取 "+sName+ "失败 ！");
		}
		iCacheSize++;
		rData[k] = r;
		rFirst = r;
		return r;
	}

	public void cleanUp() throws SQLException {
		if (iCacheSize < MAX_CACHE_SIZE) {
			return;
		}
		int count = 0, j = 0;
		while (j++ < LENGTH && iCacheSize + LENGTH > MAX_CACHE_SIZE
				&& (count * 16) < LENGTH) {
			Row r = getWorst();
			if (r == null) {
				return;
			}
			if (r.bChanged) {
				rWriter[count++] = r;
			} else {
				if (!r.canRemove()) {
					remove(r);
				}
			}
		}
		if (count != 0) {
			saveSorted(count);
		}
		for (int i = 0; i < count; i++) {
			Row r = rWriter[i];
			if (!r.canRemove()) {
				remove(r);
			}

			rWriter[i] = null;
		}
	}

	private void remove(Row r) throws SQLException {
		if (r == rLastChecked) {
		    rLastChecked = rLastChecked.rNext;
		    if (rLastChecked == r) {
			rLastChecked = null;
		    }
		}
		int k = r.iPos & MASK;

		if (rData[k] == r) {
		    Row n = r.rNext;

		    rFirst = n;

		    if (n == r || (n.iPos & MASK) != k) {
			n = null;
		    }

		    rData[k] = n;
		}
		if (r == rFirst) {
		    rFirst = rFirst.rNext;

		    if (r == rFirst) {
			rFirst = null;
		    }
		}
		r.free();

		iCacheSize--;
	    }

	private Row getWorst() throws SQLException {
		if (rLastChecked == null) {
			rLastChecked = rFirst;
		}

		Row r = rLastChecked;

		if (r == null) {
			return null;
		}

		Row candidate = r;
		int worst = Row.iCurrentAccess;

		for (int i = 0; i < 6; i++) {
			int w = r.iLastAccess;

			if (w < worst) {
				candidate = r;
				worst = w;
			}

			r = r.rNext;
		}

		rLastChecked = r.rNext;

		return candidate;
	}

	private void saveAll() throws SQLException {
		if (rFirst == null) {
			return;
		}

		Row r = rFirst;

		while (true) {
			int count = 0;
			Row begin = r;

			do {

				if (r.bChanged) {
					rWriter[count++] = r;
				}

				r = r.rNext;
			} while (r != begin && count < LENGTH);

			if (count == 0) {
				return;
			}

			saveSorted(count);

			for (int i = 0; i < count; i++) {
				rWriter[i] = null;
			}
		}
	}

	private void saveSorted(int count) throws SQLException {
		sort(rWriter, 0, count - 1);

		try {
			for (int i = 0; i < count; i++) {
				rFile.seek(rWriter[i].iPos);
				rFile.write(rWriter[i].write());
			}
		} catch (Exception e) {
			throw new SQLException("Save Sort 失败 !");
		}
	}

	private static final void sort(Row w[], int l, int r) throws SQLException {
		int i, j, p;

		while (r - l > 10) {
			i = (r + l) >> 1;
			if (w[l].iPos > w[r].iPos) {
				swap(w, l, r);
			}
			if (w[i].iPos < w[l].iPos) {
				swap(w, l, i);
			} else if (w[i].iPos > w[r].iPos) {
				swap(w, i, r);
			}
			j = r - 1;

			swap(w, i, j);

			p = w[j].iPos;
			i = l;

			while (true) {
				while (w[++i].iPos < p);
				while (w[--j].iPos > p);
				if (i >= j) {
					break;
				}
				swap(w, i, j);
			}
			swap(w, i, r - 1);
			sort(w, l, i - 1);

			l = i + 1;
		}
		for (i = l + 1; i <= r; i++) {
			Row t = w[i];
			for (j = i - 1; j >= l && w[j].iPos > t.iPos; j--) {
				w[j + 1] = w[j];
			}
			w[j + 1] = t;
		}
	}
	private static void swap(Row w[], int a, int b) {
		Row t = w[a];

		w[a] = w[b];
		w[b] = t;
	}

}
